package com.randomrpg.game.world

import java.util.Random
import java.util.ArrayList
import java.util.Collections

/**
 * A level in the dungeon
 */
@serializable @SerialVersionUID(666)
class GameMap(val level: Int, var currentPath: Path) {
  
  var visitedPaths = List(currentPath)
  
  def canGoUp = currentPath.up != null
  
  def canGoDown = currentPath.down != null
  
  def canGoLeft = currentPath.left != null
  
  def canGoRight = currentPath.right != null
  
  private def goTo(fun: () => Boolean, where: Path) = {
    if(fun()) {
      currentPath = where
      visitedPaths = currentPath :: visitedPaths
      true
    } else false
  }
  
  def up: Boolean = goTo(canGoUp _, currentPath.up)
  
  def down: Boolean = goTo(canGoDown _, currentPath.down)
  
  def left: Boolean = goTo(canGoLeft _, currentPath.left)
  
  def right: Boolean = goTo(canGoRight _, currentPath.right)
  
  def reachedExit = currentPath.position == GameMap.endPosition
  
  def visited(path: Path) = visitedPaths.contains(path)
  
  def getVisited(path: Path) = visitedPaths.find(_ == path).get
}


object GameMap {
  
  val defaultSize = (20, 10)
  val endPosition = (19, 9)
  val initialPosition = (9, 0)
  
  private val rand = new Random
  
  private val list = new ArrayList[Int]()
  list.add(0)
  list.add(1)
  list.add(2)
  list.add(3)
  
  def generateMap(level: Int): GameMap = {
    var paths = List(new Path(initialPosition))
    
    def genPaths: Unit = {
      if(!ended) {
        var path = chooseRandomPath
        val edgesNum = rand.nextInt(4)
        
        Collections.shuffle(list)
        
        var processed = false
        var i = 0
        while(i < edgesNum) {
          var direction = list.get(i)
          var pos = validEdge(path, direction)
          if(pos != null) {
            var newPath = new Path(pos)
            paths = newPath :: paths
            path.addPath(newPath, direction)
            processed = true
          }
          i += 1
        }
        if(processed) {
          path.processed = true
        }
        
        genPaths
      }
    }
    
    def ended = paths.find(_.position == endPosition).isDefined
    
    def chooseRandomPath: Path = {
      var path = paths(rand.nextInt(paths.size))
      var i = 1000000
      while(path.processed && i > 0) { path = paths(rand.nextInt(paths.size)); i -= 1}
      path
    }
    
    def validEdge(path: Path, edge: Int): Tuple2[Int, Int] = {
      edge match {
        case 0 => validUpEdge(path)
        case 1 => validDownEdge(path)
        case 2 => validLeftEdge(path)
        case 3 => validRightEdge(path)
      }
    }
    
    def validDirectionEdge(func: () => Boolean, tup: Tuple2[Int, Int]): Tuple2[Int, Int] = {
      if(func()) {
        if(!ocupied(tup)) return tup
      }
      null
    }
    
    def validUpEdge(path: Path): Tuple2[Int, Int] = {
      validDirectionEdge(path.canGoUp _, (path.position._1, path.position._2 + 1))
    }
    
    def validDownEdge(path: Path): Tuple2[Int, Int] = {
      validDirectionEdge(path.canGoDown _, (path.position._1, path.position._2 - 1))
    }
    
    def validLeftEdge(path: Path): Tuple2[Int, Int] = {
      validDirectionEdge(path.canGoLeft _, (path.position._1 - 1, path.position._2))
    }
    
    def validRightEdge(path: Path): Tuple2[Int, Int] = {
      validDirectionEdge(path.canGoRight _, (path.position._1 + 1, path.position._2))
    }
    
    def ocupied(pos: Tuple2[Int, Int]): Boolean = {
      paths.foreach { path =>
        if(path.position == pos) return true
      }
      false
    }
    
    genPaths
    return new GameMap(level, paths.last)
  }
  
  private val quad = new Array[Array[Tuple3[String, String, String]]](10)
  for(i <- 0 until 10) {
    quad(i) = new Array(20)
  }
  
  /**
   * Prints the map to stdout formatted to the player
   * @param map map to be printed
   */
  def printGameMap(map: GameMap) = {
    
    for(i <- 0 until 10; j <- 0 until 20) {
      val visited = map.visited(new Path(j, i))
      var up = "   |"
      var down = "   |"
      var left = " "
      var right = " |"
      if(visited) {
        val pat = map.getVisited(new Path(j, i))
        if(pat.up != null) up = " ^ |"
	    if(pat.down != null) down = " v |"
	    if(pat.left != null) left = "<"
	    if(pat.right != null) right = ">|"
	        
	    if(map.currentPath == pat) left += "#"
      }
      if(left.size == 1) {
        if(i == 9 && j == 19) left += "@"
        else left += " "
      }
      quad(i)(j) = (up, left + right, down)
    }
    
    println("Dungeon Level " + map.level + "/20" + """
Legend:
    # - player     @ - exit
    ^ - path up    v - path down
    < - path left  > - path right
""")
    
    var index = 9
    while(index >= 0) {
      var arr = quad(index)
      println("--------------------------------------------------------------------------------")
      arr.foreach(tup => print(tup._1))
      println
      arr.foreach(tup => print(tup._2))
      println
      arr.foreach(tup => print(tup._3))
      println
      index -= 1
    }
  }
}